/* Copyright ©, 2022, ECARX (Hubei) Technology Co., Ltd. All rights reserved.
 *
 * NOTICE: This file contains information which is confidential and proprietary to ECARX
 * (Hubei) Technology Co., Ltd. and/or other developers. No license to any intellectual
 * property rights contained within this file is either granted or implied except as may be
 * provided in an agreement with ECARX (Hubei) Technology Co., Ltd.
 *
 * Any unauthorized copying or distribution of content from this file in whole or in part
 * is prohibited.
 */
#ifndef ECARX_VHAL_CORE_READ_PROPERTY_BUILD_H
#define ECARX_VHAL_CORE_READ_PROPERTY_BUILD_H

#include <functional>
#include <type_traits>
#include <vector>

#include "cstdint"
#include "vhal_core/property/TimerBuilder.h"
#include "vhal_core/utils/convert.h"

namespace ecarx::vhal::core::property {

template <typename _TY>
class ReadOnlyAreaBuild;

/**
 * @brief Read Only propery builder
 * 
 * @tparam _TY property value type
 */
template <typename _TY>
class ReadPropertyBuilder {
    IProperty* mProperty;

  public:
    ReadPropertyBuilder(IProperty* property) : mProperty(property) {}

    /**
     * @brief sample rate  config
     * 
     * @param minSampleRate unit(Hz)
     * @param maxSampleRate unit(Hz)
     * @return ReadPropertyBuilder<_TY>& 
     */
    ReadPropertyBuilder<_TY>& sampleRate(const float minSampleRate, const float maxSampleRate) {
        mProperty->sampleRate(minSampleRate, maxSampleRate);
        return *this;
    }

    /**
     * @brief For vendor MIXED type properties
     * @brief For example:
     * {@code configArray = {1, 1, 1, 3, 0, 0, 0, 0, 0}} indicates the property has
     * a String value, a Boolean value, an Integer value and an array with 3 integers.
     * @param oneStr true indicates the property has a String value
     * @param oneBool true indicates the property has a Boolean value .
     * @param oneInt true indicates the property has an Integer value.
     * @param sizeInt the number indicates the size of Integer[] in the property.
     * @param oneLong true indicates the property has a Long value.
     * @param sizeLong the number indicates the size of Long[] in the property.
     * @param oneFloat true indicates the property has a Float value.
     * @param sizeFloat the number indicates the size of Float[] in the property.
     * @param sizeByte the number indicates the size of byte[] in the property.
     * @return ReadPropertyBuilder<_TY>& 
     */
    ReadPropertyBuilder<_TY>& mixedTypeConfig(bool oneStr, bool oneBool, bool oneInt,
                                              int32_t sizeInt, bool oneLong, int32_t sizeLong,
                                              bool oneFloat, int32_t sizeFloat, int32_t sizeByte) {
        if (std::is_same_v<_TY, MixedValue>) {
            mProperty->mixedTypeConfig(oneStr, oneBool, oneInt, sizeInt, oneLong, sizeLong,
                                       oneFloat, sizeFloat, sizeByte);
        }
        return *this;
    }

    /**
     * @brief configarray config
     * 
     * @param array 
     * @return ReadPropertyBuilder<_TY>& 
     */
    ReadPropertyBuilder<_TY>& configArray(std::vector<int32_t> array) {
        mProperty->configArray(array);
        return *this;
    }

    /**
     * @brief create area
     * 
     * @tparam AREA 
     * @param area area
     * @return ReadOnlyAreaBuild<_TY> 
     */
    template <typename AREA>
    ReadOnlyAreaBuild<_TY> area(AREA&& area) {
        auto areaId = static_cast<int32_t>(area);
        constexpr const auto globalArea = toInt(VehicleArea::GLOBAL);
        return ReadOnlyAreaBuild<_TY>(new Area(mProperty, areaId == globalArea ? 0 : areaId));
    }
};

/**
 * @brief read only property's value builder
 * 
 * @tparam _TY 
 */
template <typename _TY>
class ReadPropValueBuilder {
    Area* mArea;

  public:
    ReadPropValueBuilder(Area* area) : mArea(area) {}

    template <typename PROP, typename AREA>
    ReadPropValueBuilder<_TY>& addDependentProperty(PROP propertyId, AREA areaId) {
        mArea->addProperties(toInt(propertyId), static_cast<int32_t>(areaId));
        return *this;
    }

    /**
     * @brief define property value by signals
     * 
     * @tparam SIGNALS 
     * @param fn callback function, called when signal changed 
     * @param signal The signals that needs to trigger the callback function
     * @return TimerBuilder<_TY, ReadPropertyBuilder<_TY>> 
     */
    template <typename... SIGNALS>
    TimerBuilder<_TY, ReadPropertyBuilder<_TY>> withValueBySignals(PropValueFn<_TY>&& fn,
                                                                   SIGNALS... signal) {
        if constexpr (std::is_same_v<_TY, bool> || std::is_enum_v<_TY> ||
                      std::is_same_v<_TY, int32_t>) {
            auto signalCallback = [fn](Area* area, int64_t timestamp) {
                auto& current = area->getValue();
                _TY newValue = fn(_TY(current.value.int32Values[0]));
                area->setInt32Value(static_cast<int32_t>(newValue));
            };
            (mArea->registerSignalCallback(toInt(signal), signalCallback), ...);
        } else if constexpr (std::is_same_v<_TY, std::vector<int32_t>>) {
            auto signalCallback = [fn](Area* area, int64_t timestamp) {
                auto& current = area->getValue();
                _TY newValue = fn(current.value.int32Values);
                area->setInt32Values(newValue);
            };
            (mArea->registerSignalCallback(toInt(signal), signalCallback), ...);
        } else if constexpr (std::is_same_v<_TY, int64_t>) {
            auto signalCallback = [fn](Area* area, int64_t timestamp) {
                auto& current = area->getValue();
                _TY newValue = fn(current.value.int64Values[0]);
                area->setInt64Value(newValue);
            };
            (mArea->registerSignalCallback(toInt(signal), signalCallback), ...);
        } else if constexpr (std::is_same_v<_TY, std::vector<int64_t>>) {
            auto signalCallback = [fn](Area* area, int64_t timestamp) {
                auto& current = area->getValue();
                _TY newValue = fn(current.value.int64Values);
                area->setInt64Values(newValue);
            };
            (mArea->registerSignalCallback(toInt(signal), signalCallback), ...);
        } else if constexpr (std::is_same_v<_TY, float>) {
            auto signalCallback = [fn](Area* area, int64_t timestamp) {
                auto& current = area->getValue();
                _TY newValue = fn(current.value.floatValues[0]);
                area->setFloatValue(newValue);
            };
            (mArea->registerSignalCallback(toInt(signal), signalCallback), ...);
        } else if constexpr (std::is_same_v<_TY, std::vector<float>>) {
            auto signalCallback = [fn](Area* area, int64_t timestamp) {
                auto& current = area->getValue();
                _TY newValue = fn(current.value.floatValues);
                area->setFloatValues(newValue);
            };
            (mArea->registerSignalCallback(toInt(signal), signalCallback), ...);
        } else if constexpr (std::is_same_v<_TY, std::string>) {
            auto signalCallback = [fn](Area* area, int64_t timestamp) {
                auto& current = area->getValue();
                _TY newValue = fn(current.value.stringValue);
                area->setStringValue(newValue);
            };
            (mArea->registerSignalCallback(toInt(signal), signalCallback), ...);
        } else if constexpr (std::is_same_v<_TY, std::vector<uint8_t>>) {
            auto signalCallback = [fn](Area* area, int64_t timestamp) {
                auto& current = area->getValue();
                _TY newValue = fn(current.value.bytes);
                area->setBytes(newValue);
            };
            (mArea->registerSignalCallback(toInt(signal), signalCallback), ...);
        } else if constexpr (std::is_same_v<_TY, MixedValue>) {
            auto signalCallback = [fn](Area* area, int64_t timestamp) {
                auto& current = area->getValue();
                _TY newValue = fn(current.value);
                area->setMixedValue(newValue);
            };
            (mArea->registerSignalCallback(toInt(signal), signalCallback), ...);
        } else {
            auto signalCallback = [fn](Area* area, int64_t timestamp) {
                auto& current = area->getValue();
                _TY value = utils::toValue<_TY>(current.value.bytes);
                _TY newValue = fn(value);
                std::vector<uint8_t> bytes = utils::toBytes(newValue);
                area->setBytes(bytes);
            };
            (mArea->registerSignalCallback(toInt(signal), signalCallback), ...);
        }
        return TimerBuilder<_TY, ReadPropertyBuilder<_TY>>(mArea);
    }
};

/**
 * @brief read only property status builder.
 * 
 * @tparam _TY 
 */
template <typename _TY>
class ReadPropStatusBuilder {
    Area* mArea;

  public:
    ReadPropStatusBuilder(Area* area) : mArea(area) {}

    /**
     * @brief immutable status
     * 
     * @param status PropertyStatus
     * @return ReadPropValueBuilder<_TY> 
     */
    ReadPropValueBuilder<_TY> immutableStatus(PropertyStatus status) {
        mArea->updateStatus(status);
        return ReadPropValueBuilder<_TY>(mArea);
    }

    /**
     * @brief define property status by signals
     * 
     * @tparam SIGNALS signals
     * @param statusFn callback function, called when signal changed 
     * @param signal signals
     * @return ReadPropValueBuilder<_TY> 
     */
    template <typename... SIGNALS>
    ReadPropValueBuilder<_TY> withStatusBySignals(PropStatusFn&& statusFn, SIGNALS... signal) {
        auto signalCallback = [statusFn](Area* area, int64_t timestamp) {
            PropertyStatus newStatus = statusFn(area->getStatus());
            area->updateStatus(newStatus);
        };
        mArea->setStatusFn([statusFn](Area* area) { return statusFn(area->getStatus()); });
        (mArea->registerSignalCallback(toInt(signal), signalCallback), ...);
        return ReadPropValueBuilder<_TY>(mArea);
    }
};

/**
 * @brief read only property Area builder
 * 
 * @tparam _TY 
 */
template <typename _TY>
class ReadOnlyAreaBuild {
    Area* mArea;

  public:
    ReadOnlyAreaBuild(Area* area) : mArea(area) {}

    /**
     * @brief The area config range
     * 
     * @param min 
     * @param max
     * @return ReadOnlyAreaBuild<_TY>& 
     */
    ReadOnlyAreaBuild<_TY>& range(_TY min __attribute__((unused)), _TY max __attribute__((unused))) {
        if constexpr (std::is_enum_v<_TY>) {
            mArea->getAreaConfig().minInt32Value = static_cast<int32_t>(min);
            mArea->getAreaConfig().maxInt32Value = static_cast<int32_t>(max);
        }
        return *this;
    }

    /**
     * @brief The area VehiclePropValue commit mode
     * 
     * @param min 
     * @param max
     * @return ReadOnlyAreaBuild<_TY>& 
     */
    ReadOnlyAreaBuild<_TY>& vehiclePropValueCommitMode(CommitMode mode) {
        mArea->setPropValueCommitMode(mode);
        return *this;
    }

    /**
     * @brief The property init value of this area.
     * 
     * @param fn provide init value
     * @return ReadPropStatusBuilder<_TY> 
     */
    ReadPropStatusBuilder<_TY> initValue(InitFn<_TY> fn) {
        if constexpr (std::is_enum_v<_TY>) {
            mArea->setInitValueFn(
                [fn](Area* area) { area->setInt32Value(static_cast<int32_t>(fn())); });
        } else {
            mArea->setInitValueFn([fn](Area* area) {
                auto value = utils::toBytes(fn());
                area->setBytes(value);
            });
        }

        return ReadPropStatusBuilder<_TY>(mArea);
    }
};

template <>
inline ReadOnlyAreaBuild<int32_t>& ReadOnlyAreaBuild<int32_t>::range(int32_t min, int32_t max) {
    mArea->getAreaConfig().minInt32Value = min;
    mArea->getAreaConfig().maxInt32Value = max;
    return *this;
}

template <>
inline ReadOnlyAreaBuild<int64_t>& ReadOnlyAreaBuild<int64_t>::range(int64_t min, int64_t max) {
    mArea->getAreaConfig().minInt64Value = min;
    mArea->getAreaConfig().maxInt64Value = max;
    return *this;
}

template <>
inline ReadOnlyAreaBuild<float>& ReadOnlyAreaBuild<float>::range(float min, float max) {
    mArea->getAreaConfig().minFloatValue = min;
    mArea->getAreaConfig().maxFloatValue = max;
    return *this;
}

template <>
inline ReadPropStatusBuilder<bool> ReadOnlyAreaBuild<bool>::initValue(InitFn<bool> fn) {
    mArea->setInitValueFn([fn](Area* area) { area->setBoolValue(fn()); });
    return ReadPropStatusBuilder<bool>(mArea);
}

template <>
inline ReadPropStatusBuilder<int32_t> ReadOnlyAreaBuild<int32_t>::initValue(InitFn<int32_t> fn) {
    mArea->setInitValueFn([fn](Area* area) { area->setInt32Value(fn()); });
    return ReadPropStatusBuilder<int32_t>(mArea);
}

template <>
inline ReadPropStatusBuilder<std::vector<int32_t>>
ReadOnlyAreaBuild<std::vector<int32_t>>::initValue(InitFn<std::vector<int32_t>> fn) {
    mArea->setInitValueFn([fn](Area* area) {
        auto value = fn();
        area->setInt32Values(value);
    });
    return ReadPropStatusBuilder<std::vector<int32_t>>(mArea);
}

template <>
inline ReadPropStatusBuilder<int64_t> ReadOnlyAreaBuild<int64_t>::initValue(InitFn<int64_t> fn) {
    mArea->setInitValueFn([fn](Area* area) { area->setInt64Value(fn()); });
    return ReadPropStatusBuilder<int64_t>(mArea);
}

template <>
inline ReadPropStatusBuilder<std::vector<int64_t>>
ReadOnlyAreaBuild<std::vector<int64_t>>::initValue(InitFn<std::vector<int64_t>> fn) {
    mArea->setInitValueFn([fn](Area* area) {
        auto value = fn();
        area->setInt64Values(value);
    });
    return ReadPropStatusBuilder<std::vector<int64_t>>(mArea);
}

template <>
inline ReadPropStatusBuilder<float> ReadOnlyAreaBuild<float>::initValue(InitFn<float> fn) {
    mArea->setInitValueFn([fn](Area* area) { area->setFloatValue(fn()); });
    return ReadPropStatusBuilder<float>(mArea);
}

template <>
inline ReadPropStatusBuilder<std::vector<float>> ReadOnlyAreaBuild<std::vector<float>>::initValue(
    InitFn<std::vector<float>> fn) {
    mArea->setInitValueFn([fn](Area* area) {
        auto value = fn();
        area->setFloatValues(value);
    });

    return ReadPropStatusBuilder<std::vector<float>>(mArea);
}

template <>
inline ReadPropStatusBuilder<std::string> ReadOnlyAreaBuild<std::string>::initValue(
    InitFn<std::string> fn) {
    mArea->setInitValueFn([fn](Area* area) {
        std::string str = fn();
        area->setStringValue(str);
    });
    return ReadPropStatusBuilder<std::string>(mArea);
}

template <>
inline ReadPropStatusBuilder<std::vector<uint8_t>>
ReadOnlyAreaBuild<std::vector<uint8_t>>::initValue(InitFn<std::vector<uint8_t>> fn) {
    mArea->setInitValueFn([fn](Area* area) {
        auto value = fn();
        area->setBytes(value);
    });
    return ReadPropStatusBuilder<std::vector<uint8_t>>(mArea);
}

template <>
inline ReadPropStatusBuilder<MixedValue> ReadOnlyAreaBuild<MixedValue>::initValue(
    InitFn<MixedValue> fn) {
    mArea->setInitValueFn([fn](Area* area) {
        auto value = fn();
        area->setMixedValue(value);
    });
    return ReadPropStatusBuilder<MixedValue>(mArea);
}

}  // namespace ecarx::vhal::core::property

#endif
